//
// Created by ych on 2/14/24.
//
#include "lexer.h"
#include "../machine/ych_state.h"

#define LEXER_STRING_BUFFER_SIZE 64

void freeToken(Token *token) {
    if (token->token_type == TK_STRING_V || token->token_type == TK_NAME) {
        freeYString(token->token_value.string);
        free(token->token_value.string);
    }
}

void initLexer(Lexer *lexer, YState *y_state) {
    lexer->y_state = y_state;
    lexer->reader = &y_state->reader;
}

void freeLexer(Lexer *lexer) {
    lexer->y_state = NULL;
    lexer->reader = NULL;
}

void lexerThrow(Lexer *lexer, size_t length, LError type) {
    char buffer[LEXER_STRING_BUFFER_SIZE];
    Error error;
    error.code = EXCEPTION_TOKENILLEGAL;
    cutString(&error.info, lexer->reader->ch, -length, length);
    sprintf(buffer, " line %zu:", lexer->reader->line);
    appendString(&error.info, buffer);
    switch (type) {
        case L_CH_ERROR:
            appendString(&error.info, "CHARACTER ERROR");
            break;
        case L_ID_ERROR:
            appendString(&error.info, "IDENTIFIER ERROR");
            break;
        case L_NUM_ERROR:
            appendString(&error.info, "NUMBER ERROR");
            break;
        case L_QM_ERROR:
            appendString(&error.info, "STRING ERROR");
            break;
        default:
            appendString(&error.info, "LEXER ERROR");
    }
    pushErrorStack(&lexer->y_state->errors, error);
}

typedef struct Reserved {
    char *string;
    TK_ tk;
} Reserved;

const Reserved reserveds[] = {{"Func",   TK_FUNCTION},
                              {"break",  TK_BREAK},
                              {"else",   TK_ELSE},
                              {"false",  TK_FALSE},
                              {"if",     TK_IF},
                              {"import", TK_IMPORT},
                              {"return", TK_RETURN},
                              {"true",   TK_TRUE},
                              {"var",    TK_VAR},
                              {"while",  TK_WHILE}};


static int binarySearch(const Reserved arr[], const char *target) {
    int low = 0, high = sizeof(reserveds) / sizeof(Reserved) - 1;
    while (low <= high) {
        int mid = (low + high) / 2;
        int cmp = strcmp(arr[mid].string, target);
        if (cmp == 0) {
            return mid;
        } else if (cmp < 0) {
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }
    return -1;
}

static TK_ getTokenType(String *string) {//TODO 二分查找减少时间复杂°
    int index = binarySearch(reserveds, string->data);
    if (index == -1)
        return TK_NAME;
    else
        return reserveds[index].tk;
}

static int isEnter(char ch) { return ch == '\n'; }

static int isBlank(char ch) { return ch == ' ' || ch == '\n' || ch == '\t'; }

static int isLetter(char ch) { return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'; }

static int isDot(char ch) { return ch == '.'; }

static int isDigital(char ch) { return '0' <= ch && ch <= '9'; }

static int isEq(char ch) { return ch == '='; }

static int isAst(char ch) { return ch == '*'; }

static int isEx(char ch) { return ch == '!'; }

static int isQm(char ch) { return ch == '\"'; }

static int isGreater(char ch) { return ch == '>'; }

static int isLess(char ch) { return ch == '<'; }

static int isEof(char ch) { return ch == '\0'; }

static int isEc(char ch) { return ch == '\\'; }

int isSingle(char ch) {
    return ch == '+' || ch == '-' || ch == '/' || ch == '{' || ch == '}' ||
           ch == ';' || ch == ',' || ch == '(' || ch == ')' || ch == '|' ||
           ch == '&' || ch == '%' || ch == '[' || ch == ']' || ch == ':';
}

void identifierToken(Lexer *lexer, Token *token, size_t pos) {
    size_t length;
    char ch = getChar(lexer->reader);
    while (isLetter(ch) || isDigital(ch)) {
        nextChar(lexer->reader);
        ch = getChar(lexer->reader);
    }
    length = lexer->reader->pos - pos;
    String *literal_string = malloc(sizeof(String));
    cutString(literal_string, lexer->reader->ch, -length, length);
    token->token_type = getTokenType(literal_string);
    if (token->token_type != TK_NAME) {
        deleteYString(literal_string);
    } else {
        token->token_value.string = literal_string;
    }
}

void illegalToken(Lexer *lexer, Token *token, size_t pos) {
    size_t length;
    char ch = getChar(lexer->reader);
    while (!isBlank(ch) && !isEof(ch)) {
        nextChar(lexer->reader);
        ch = getChar(lexer->reader);
    }
    length = lexer->reader->pos - pos;
    lexerThrow(lexer, length, L_CH_ERROR);
    token->token_type = TK_ILLEGAL;
}

void numberToken(Lexer *lexer, Token *token, size_t pos) {
    size_t length;
    int has_dot = False, is_illegal = False;
    char ch = getChar(lexer->reader);
    while (isDigital(ch) || isDot(ch) || isLetter(ch)) {
        if (isDot(ch)) {
            if (has_dot) break;
            has_dot = True;
        }
        if (isLetter(ch)) is_illegal = True;
        nextChar(lexer->reader);
        ch = getChar(lexer->reader);
    }
    length = lexer->reader->pos - pos;
    if (is_illegal) {
        lexerThrow(lexer, length, L_ID_ERROR);
    } else {
        String *literal_string = malloc(sizeof(String));
        cutString(literal_string, lexer->reader->ch, -length, length);
        if (has_dot) {
            token->token_value.f = atof(literal_string->data);
            token->token_type = TK_FLOAT_V;
        } else {
            token->token_value.i = atoll(literal_string->data);
            token->token_type = TK_INT_V;
        }
        freeYString(literal_string);
    }
}

void singleToken(Lexer *lexer, Token *token) {
    char ch = getChar(lexer->reader);
    token->token_type = ch;
    nextChar(lexer->reader);
}

void dotToken(Lexer *lexer, Token *token, size_t pos) {
    size_t length;
    int is_illegal = False;
    nextChar(lexer->reader);
    char ch = getChar(lexer->reader);
    if (isAst(ch)) {
        token->token_type = TK_INNER;
        nextChar(lexer->reader);
    } else if (isDigital(ch)) {
        while (isDigital(ch) || isLetter(ch)) {
            if (isLetter(ch)) is_illegal = True;
            nextChar(lexer->reader);
            ch = getChar(lexer->reader);
        }
        length = lexer->reader->pos - pos;
        if (is_illegal) {
            lexerThrow(lexer, length, L_ID_ERROR);
        } else {
            String *literal_string = malloc(sizeof(String));
            cutString(literal_string, lexer->reader->ch, -length, length);
            token->token_value.f = atof(literal_string->data);
            token->token_type = TK_FLOAT_V;
            freeYString(literal_string);
        }
    } else {
        token->token_type = '.';
    }
}

void astToken(Lexer *lexer, Token *token) {
    nextChar(lexer->reader);
    char ch = getChar(lexer->reader);
    if (isAst(ch)) {
        token->token_type = TK_POW;
        nextChar(lexer->reader);
    } else {
        token->token_type = '*';
    }
}

void lessToken(Lexer *lexer, Token *token) {
    nextChar(lexer->reader);
    char ch = getChar(lexer->reader);
    if (isEq(ch)) {
        token->token_type = TK_LESSEQUAL;
        nextChar(lexer->reader);
    } else {
        token->token_type = '<';
    }
}

void greaterToken(Lexer *lexer, Token *token) {
    nextChar(lexer->reader);
    char ch = getChar(lexer->reader);
    if (isEq(ch)) {
        token->token_type = TK_GREATEREQUAL;
        nextChar(lexer->reader);
    } else {
        token->token_type = '>';
    }
}

void assignToken(Lexer *lexer, Token *token) {
    nextChar(lexer->reader);
    char ch = getChar(lexer->reader);
    if (isEq(ch)) {
        token->token_type = TK_EQUAL;
        nextChar(lexer->reader);
    } else {
        token->token_type = '=';
    }
}

void exToken(Lexer *lexer, Token *token) {
    nextChar(lexer->reader);
    char ch = getChar(lexer->reader);
    if (isEq(ch)) {
        token->token_type = TK_NOTEQUAL;
        nextChar(lexer->reader);
    } else {
        token->token_type = '!';
    }
}

static char getEc(char ch) {
    switch (ch) {
        case '0':
            return '\0';
        case 'n':
            return '\n';
        case 't':
            return '\t';
        case '\"':
            return '\"';
        default:
            return -1;
    }
}

void stringToken(Lexer *lexer, Token *token, size_t pos) {
    size_t length;
    int is_illegal = False;
    nextChar(lexer->reader);
    char ch = getChar(lexer->reader);
    String *literal_string = malloc(sizeof(String));
    initString(literal_string);
    while (!isQm(ch)) {
        if (isEof(ch) || isEnter(ch)) {
            is_illegal = True;
            break;
        }
        if (!isEc(ch)) {
            appendChar(literal_string, ch);
        } else {
            nextChar(lexer->reader);
            ch = getChar(lexer->reader);
            char ec = getEc(ch);
            if (ec == -1) {
                is_illegal = True;
                break;
            }
            appendChar(literal_string, ec);
        }
        nextChar(lexer->reader);
        ch = getChar(lexer->reader);
    }
    if (is_illegal) {
        deleteYString(literal_string);
        length = lexer->reader->pos - pos;
        lexerThrow(lexer, length, L_CH_ERROR);
        token->token_type = TK_ILLEGAL;
    } else {
        nextChar(lexer->reader);
        token->token_value.string = literal_string;
        token->token_type = TK_STRING_V;
    }
}


void eofToken(Lexer *lexer, Token *token) {
    token->token_type = TK_EOF;
}

Token getNextToken(Lexer *lexer) {
    Token token;
    skipBlanks(lexer);
    size_t pos = lexer->reader->pos;
    char ch = getChar(lexer->reader);
    if (isLetter(ch)) {
        identifierToken(lexer, &token, pos);
    } else if (isDigital(ch)) {
        numberToken(lexer, &token, pos);
    } else if (isDot(ch)) {
        dotToken(lexer, &token, pos);
    } else if (isAst(ch)) {
        astToken(lexer, &token);
    } else if (isLess(ch)) {
        lessToken(lexer, &token);
    } else if (isGreater(ch)) {
        greaterToken(lexer, &token);
    } else if (isEq(ch)) {
        assignToken(lexer, &token);
    } else if (isEx(ch)) {
        exToken(lexer, &token);
    } else if (isSingle(ch)) {
        singleToken(lexer, &token);
    } else if (isQm(ch)) {
        stringToken(lexer, &token, pos);
    } else if (isEof(ch)) {
        eofToken(lexer, &token);
    } else {
        illegalToken(lexer, &token, pos);
    }
    return token;
}

void skipBlanks(Lexer *lexer) {
    while (isBlank(getChar(lexer->reader)))
        nextChar(lexer->reader);
}